<template>
  <view
    class="xb-image"
    :style="[xbImageStyles, styles]"
    :class="{ 'xb-image-transition': ifTransition }"
  >
    <image
      class="xb-image-module"
      :class="{ [className]: true }"
      :src="src"
      :mode="mode"
      :lazy-load="lazyLoad"
      :fade-show="fadeShow"
      :webp="webp"
      :show-menu-by-longpress="showMenuByLongpress"
      :draggable="draggable"
      @error="error"
      @load="load"
      v-if="imageSrc"
    ></image>
    <view
      :class="[
        slots.loading ? 'xb-image-loading-slot' : 'xb-image-loading',
        { 'fade-out-animation': loadingStatus },
      ]"
      v-if="showLoading"
    >
      <slot name="loading"></slot>
    </view>
    <view class="xb-image-error" v-if="loadingErrorStatus" @click.stop="reload">
      <template v-if="!slots.error">
        <text
          class="iconfont_xbimage icon-pic-lose"
          style="color: #999999; font-size: 100rpx"
        ></text>
        <view class="xb-image-error-tips">{{ ifReload ? '加载失败,点击重试' : ' ' }}</view>
      </template>
      <slot name="error"></slot>
    </view>
  </view>
</template>

<script setup>
/**
 * xb-image 图片组件
 * @description 用于加载图片显示
 * @property {String|Number} width 宽度
 * @property {String|Number} height 高度
 * @property {String} src 图片资源地址
 * @property {String} mode 图片裁剪、缩放模式
 * 	@value scaleToFill 不保持纵横比缩放图片，使图片的宽高完全拉伸至填满 image 元素
 * 	@value aspectFit 保持纵横比缩放图片，使图片的长边能完全显示出来。也就是说，可以完整地将图片显示出来。
 * 	@value aspectFill 保持纵横比缩放图片，只保证图片的短边能完全显示出来。也就是说，图片通常只在水平或垂直方向是完整的，另一个方向将会发生截取。
 * 	@value widthFix 宽度不变，高度自动变化，保持原图宽高比不变
 * 	@value heightFix 高度不变，宽度自动变化，保持原图宽高比不变
 * 	@value top	 不缩放图片，只显示图片的顶部区域
 * 	@value bottom 不缩放图片，只显示图片的底部区域
 * 	@value center 不缩放图片，只显示图片的中间区域
 * 	@value left 不缩放图片，只显示图片的左边区域
 * 	@value right 不缩放图片，只显示图片的右边区域
 *  @value topleft 不缩放图片，只显示图片的左上边区域
 * 	@value topright 不缩放图片，只显示图片的右上边区域
 * 	@value bottomleft 不缩放图片，只显示图片的左下边区域
 * 	@value bottomright 不缩放图片，只显示图片的右下边区域
 * @property {Boolean} lazyLoad 图片懒加载。只针对page与scroll-view下的image有效
 * @property {Boolean} fadeShow 图片显示动画效果
 * @property {Boolean} webp 是否支持 webP 格式
 * @property {Boolean} showMenuByLongpress 开启长按图片显示识别小程序码菜单
 * @property {Boolean} draggable 鼠标长按是否能拖动图片(仅H5平台)
 * @property {Boolean} transition 是否使用过渡
 * @property {Boolean} ifReload 加载失败是否允许重新加载
 * @property {Object} styles 样式
 * @event {Function} error 图片加载错误
 * @event {Function} load 图片加载成功
 */

import { computed, nextTick, onMounted, ref, getCurrentInstance, useSlots, toRef, watch } from 'vue'
const slots = useSlots()
const props = defineProps({
  width: [String, Number],
  height: [String, Number],
  src: String,
  styles: Object,
  mode: {
    type: String,
    default: 'scaleToFill',
  },
  lazyLoad: {
    type: Boolean,
    default: false,
  },
  fadeShow: {
    type: Boolean,
    default: true,
  },
  webp: {
    type: Boolean,
    default: false,
  },
  showMenuByLongpress: {
    type: Boolean,
    default: false,
  },
  draggable: {
    type: Boolean,
    default: true,
  },
  transition: {
    type: Boolean,
    default: true,
  },
  ifReload: {
    type: Boolean,
    default: true,
  },
})

const imageSrc = ref()

watch(
  () => props.src,
  (val) => {
    imageSrc.value = val
  },
  {
    immediate: true,
  },
)

const showLoading = ref(true)

const widthString = computed(() => {
  if (props.width) {
    if (typeof props.width === 'string') {
      return props.width
    } else {
      return `${props.width}px`
    }
  } else {
    return ''
  }
})

const heightString = computed(() => {
  if (props.height) {
    if (typeof props.height === 'string') {
      return props.height
    } else {
      return `${props.height}px`
    }
  } else {
    return ''
  }
})

const xbImageStyles = computed(() => {
  let styles = {
    width: widthString.value,
    height: heightString.value,
  }
  if (props.mode == 'widthFix') {
    styles.height = imageHeight.value ? `${imageHeight.value}px` : heightString.value || `20px`
  }
  if (props.mode == 'heightFix') {
    styles.width = imageWidth.value ? `${imageWidth.value}px` : widthString.value || `20px`
  }
  return styles
})

const _ifTransition = ref(true)

const ifTransition = computed(() => {
  if (
    props.transition &&
    (props.mode == 'widthFix' || props.mode == 'heightFix') &&
    _ifTransition.value
  ) {
    return true
  }
  return false
})

let instance = null

onMounted(() => {
  if (!props.src) {
    loadingStatus.value = true
    loadingErrorStatus.value = true
  }
  instance = getCurrentInstance()
})

const emit = defineEmits(['error', 'load'])

const loadingStatus = ref(false)
const loadingErrorStatus = ref(false)

const error = (e) => {
  loadingStatus.value = true
  loadingErrorStatus.value = true
  emit('error', e)
}

const className = ref('')

const imageHeight = ref(null)
const imageWidth = ref(null)

const load = (e) => {
  if (props.mode == 'widthFix' || props.mode == 'heightFix') {
    className.value =
      'class_' +
      String(e.timeStamp).replace(/\./g, '') +
      String(Math.floor(Math.random() * 1000) + 1)
    const originalWidth = e.detail.width
    const originalHeight = e.detail.height
    nextTick(() => {
      const getImageHeight = () => {
        const query = uni.createSelectorQuery().in(instance)
        query
          .select(`.${className.value}`)
          .boundingClientRect((data) => {
            if (props.mode == 'widthFix') {
              imageHeight.value = resizeImage(
                originalWidth,
                originalHeight,
                data.width,
                'width',
              ).height
            } else {
              imageWidth.value = resizeImage(
                originalWidth,
                originalHeight,
                data.height,
                'height',
              ).width
            }
          })
          .exec()
      }
      getImageHeight()
      loadingStatus.value = true
      setTimeout(() => {
        showLoading.value = false
      }, 300)
      emit('load', e)
      uni.onWindowResize((e) => {
        _ifTransition.value = false
        nextTick(() => {
          getImageHeight()
        })
      })
    })
  } else {
    loadingStatus.value = true
    emit('load', e)
  }
}

const resizeImage = (originalWidth, originalHeight, targetPx, type) => {
  if (type == 'width') {
    const scaleRatio = targetPx / originalWidth
    const adjustedHeight = Math.round(originalHeight * scaleRatio)
    return {
      width: targetPx,
      height: adjustedHeight,
    }
  } else {
    const scaleRatio = targetPx / originalHeight
    const adjustedWidth = Math.round(originalWidth * scaleRatio)
    return {
      width: adjustedWidth,
      height: targetPx,
    }
  }
}

const skeleton = [
  {
    type: 'flex',
    num: 1,
    children: [
      {
        type: 'line',
        num: 1,
        style: ['width:100%;height:100%'],
      },
    ],
    style: ['width:100%;height:100%;flex:1'],
  },
]

const reload = () => {
  if (props.ifReload) {
    loadingStatus.value = false
    loadingErrorStatus.value = false
    showLoading.value = true
    imageSrc.value = ''
    setTimeout(() => {
      imageSrc.value = props.src
    }, 1)
  }
}

defineExpose({
  reload,
})
</script>

<style lang="scss">
@import '../../icon/iconfont.css';

.xb-image {
  position: relative;
  overflow: hidden;

  &.xb-image-transition {
    transition: 0.3s;
  }
}

.xb-image-module {
  border-radius: 20rpx;
  width: 100%;
  height: 100%;
  position: relative;
  z-index: 1;
  float: left;
}

.xb-image-error {
  border-radius: 20rpx;
  margin: 0 10rpx;
  background: linear-gradient(90deg, #f1f2f4 25%, #e6e6e6 37%, #f1f2f4 50%);
  background-size: 400% 100%;
  position: absolute;
  inset: -10rpx;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;

  .xb-image-error-tips {
    font-size: 20rpx;
    color: #999;
    margin-top: 5rpx;
    width: 100%;
    padding: 0 10rpx;
    box-sizing: border-box;
    text-align: center;
  }
}

.xb-image-loading {
  background: linear-gradient(90deg, #f1f2f4 25%, #e6e6e6 37%, #f1f2f4 50%);
  background-size: 400% 100%;
  animation: skeleton 1.8s ease infinite;
  position: absolute;
  inset: 0;
  z-index: 2;
}

.xb-image-loading-slot {
  position: absolute;
  inset: 0;
  z-index: 2;
}

@keyframes skeleton {
  0% {
    background-position: 100% 50%;
  }

  100% {
    background-position: 0% 50%;
  }
}

.fade-out-animation {
  animation: fadeOut 0.3s ease-in-out forwards;
}

@keyframes fadeOut {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}
</style>
